﻿using System;
using FluentAssertions;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1;
using O1.Data;
using O1.Data.Traversal;
using O1.Data.Traversal.Fakes;

namespace Tests.O1.Data.Traversal
{
    [TestClass]
    public class ProjectionTests
    {
        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionPropertyInterestMatchesInitialization()
        {
            new Projection(PropertyInterests.All).PropertyInterest.Should().Be(PropertyInterests.All);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionProjectsWithoutExceptionByDefault()
        {
            var subject = new Projection(PropertyInterests.All);

            Action test = () =>
            {
                subject.StartVertex(new CellAddress());
                subject.StartEdge(new Edge(), EdgeDirections.Inbound);
                subject.Evaluate(new DateProperty());
                subject.Evaluate(new DateTimeProperty());
                subject.Evaluate(new DecimalProperty());
                subject.Evaluate(new HashProperty());
                subject.Evaluate(new Int16Property());
                subject.Evaluate(new Int32Property());
                subject.Evaluate(new Int64Property());
                subject.Evaluate(new StringProperty());
                subject.EndEdge(ObservationInterests.Interested);
                subject.EndVertex(ObservationInterests.Interested);
            };

            test.ShouldNotThrow();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForClearVertexStateWhenCallerInterested()
        {
            var subject = new StubProjection(PropertyInterests.None);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndVertexObservationInterests = e => count++;
            subject.EndVertex(ObservationInterests.Interested);
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredDoesNotInvokeProjectMethodForClearVertexStateWhenCallerUninterested()
        {
            var subject = new StubProjection(PropertyInterests.None);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndVertexObservationInterests = e => count++;
            subject.EndVertex(ObservationInterests.None);
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForClearVertexStateWhenCallerInterested()
        {
            var filter = new StubFilter();
            filter.EndVertexObservationInterests = d => ObservationInterests.None;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndVertexObservationInterests = e => count++;
            subject.EndVertex(ObservationInterests.Interested);
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForClearVertexStateWhenCallerUninterested()
        {
            var filter = new StubFilter();
            filter.EndVertexObservationInterests = d => ObservationInterests.Interested;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndVertexObservationInterests = e => count++;
            subject.EndVertex(ObservationInterests.None);
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForClearEdgeStateWhenCallerInterested()
        {
            var subject = new StubProjection(PropertyInterests.None);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndEdgeObservationInterests = e => count++;
            subject.EndEdge(ObservationInterests.Interested);
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredDoesNotInvokeProjectMethodForClearEdgeStateWhenCallerUninterested()
        {
            var subject = new StubProjection(PropertyInterests.None);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndEdgeObservationInterests = e => count++;
            subject.EndEdge(ObservationInterests.None);
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForClearEdgeStateWhenCallerInterested()
        {
            var filter = new StubFilter();
            filter.EndEdgeObservationInterests = d => ObservationInterests.None;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndEdgeObservationInterests = e => count++;
            subject.EndEdge(ObservationInterests.Interested);
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForClearEdgeStateWhenCallerUninterested()
        {
            var filter = new StubFilter();
            filter.EndEdgeObservationInterests = d => ObservationInterests.Interested;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEndEdgeObservationInterests = e => count++;
            subject.EndEdge(ObservationInterests.None);
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForCellAddress()
        {
            var subject = new StubProjection(PropertyInterests.None);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectCellAddress = e => count++;
            subject.StartVertex(new CellAddress(11, 13565));
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForCellAddressWhenFilterInterested()
        {
                var filter = new StubFilter();
                filter.StartVertexCellAddress = e => ObservationInterests.Interested;
                var subject = new StubProjection(filter);
                subject.CallBase = true;

                var count = 0;
                subject.ProjectCellAddress = e => count++;
                subject.StartVertex(new CellAddress(11, 13565));
                count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForCellAddressWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.StartVertexCellAddress = e => ObservationInterests.None;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectCellAddress = e => count++;
            subject.StartVertex(new CellAddress(11, 13565));
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForEdge()
        {
            var subject = new StubProjection(PropertyInterests.None);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEdgeEdgeDirections = (e, d) => count++;
            subject.StartEdge(new Edge(), EdgeDirections.None);
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForEdgeWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.StartEdgeEdgeEdgeDirections = (e, d) => ObservationInterests.Interested;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEdgeEdgeDirections = (e, d) => count++;
            subject.StartEdge(new Edge(), EdgeDirections.None);
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForEdgeWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.StartEdgeEdgeEdgeDirections = (e, d) => ObservationInterests.None;
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectEdgeEdgeDirections = (e, d) => count++;
            subject.StartEdge(new Edge(), EdgeDirections.None);
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForDatePropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.Date);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDateProperty = v => count++;
            subject.Evaluate(new DateProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForDatePropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Date;
            filter.EvaluateDateProperty = v => ObservationInterests.Interested;
            
            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDateProperty = v => count++;
            subject.Evaluate(new DateProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForDatePropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Date;
            filter.EvaluateDateProperty = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDateProperty = v => count++;
            subject.Evaluate(new DateProperty());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForDateTimePropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.DateTime);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDateTimeProperty = v => count++;
            subject.Evaluate(new DateTimeProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForDateTimePropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.DateTime;
            filter.EvaluateDateTimeProperty = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDateTimeProperty = v => count++;
            subject.Evaluate(new DateTimeProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForDateTimePropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.DateTime;
            filter.EvaluateDateTimeProperty = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDateTimeProperty = v => count++;
            subject.Evaluate(new DateTimeProperty());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForDecimalPropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.Decimal);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDecimalProperty = v => count++;
            subject.Evaluate(new DecimalProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForDecimalPropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.DateTime;
            filter.EvaluateDecimalProperty = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDecimalProperty = v => count++;
            subject.Evaluate(new DecimalProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForDecimalPropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Decimal;
            filter.EvaluateDecimalProperty = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectDecimalProperty = v => count++;
            subject.Evaluate(new DecimalProperty());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForHashPropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.Hash);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectHashProperty = v => count++;
            subject.Evaluate(new HashProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForHashPropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Hash;
            filter.EvaluateHashProperty = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectHashProperty = v => count++;
            subject.Evaluate(new HashProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForHashPropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Hash;
            filter.EvaluateHashProperty = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectHashProperty = v => count++;
            subject.Evaluate(new HashProperty());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForInt16PropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.Int16);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt16Property = v => count++;
            subject.Evaluate(new Int16Property());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForInt16PropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Int16;
            filter.EvaluateInt16Property = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt16Property = v => count++;
            subject.Evaluate(new Int16Property());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForInt16PropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Int16;
            filter.EvaluateInt16Property = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt16Property = v => count++;
            subject.Evaluate(new Int16Property());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForInt32PropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.Int32);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt32Property = v => count++;
            subject.Evaluate(new Int32Property());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForInt32PropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Int32;
            filter.EvaluateInt32Property = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt32Property = v => count++;
            subject.Evaluate(new Int32Property());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForInt32PropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Int32;
            filter.EvaluateInt32Property = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt32Property = v => count++;
            subject.Evaluate(new Int32Property());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForInt64PropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.Int64);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt64Property = v => count++;
            subject.Evaluate(new Int64Property());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForInt64PropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Int64;
            filter.EvaluateInt64Property = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt64Property = v => count++;
            subject.Evaluate(new Int64Property());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForInt64PropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.Int64;
            filter.EvaluateInt64Property = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectInt64Property = v => count++;
            subject.Evaluate(new Int64Property());
            count.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionUnfilteredInvokesProjectMethodForStringPropertyWhenInterested()
        {
            var subject = new StubProjection(PropertyInterests.String);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectStringProperty = v => count++;
            subject.Evaluate(new StringProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterInvokesProjectMethodForStringPropertyWhenFilterInterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.String;
            filter.EvaluateStringProperty = v => ObservationInterests.Interested;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectStringProperty = v => count++;
            subject.Evaluate(new StringProperty());
            count.Should().Be(1);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void ProjectionWithFilterDoesNotInvokeProjectMethodForStringPropertyWhenFilterUninterested()
        {
            var filter = new StubFilter();
            filter.PropertyInterestGet = () => PropertyInterests.String;
            filter.EvaluateStringProperty = v => ObservationInterests.None;

            var subject = new StubProjection(filter);
            subject.CallBase = true;

            var count = 0;
            subject.ProjectStringProperty = v => count++;
            subject.Evaluate(new StringProperty());
            count.Should().Be(0);
        }

        //// The following tests rely on the behavior of the CellObserver to invoke based on property interest.
        ////[TestMethod, TestCategory("Unit Test")]
        ////public void ProjectionUnfilteredDoesNotInvokeProjectMethodForDatePropertyWhenUninterested()
        ////{
        ////    var subject = new StubProjection(PropertyInterests.None);
        ////    subject.CallBase = true;

        ////    var count = 0;
        ////    subject.ProjectDateProperty = v => count++;

        ////    if (subject.PropertyInterest.HasFlag(PropertyInterests.Date)) subject.Evaluate(new DateProperty());
        ////    count.Should().Be(0);
        ////}

        ////[TestMethod, TestCategory("Unit Test")]
        ////public void ProjectionWithFilterDoesNotInvokeProjectMethodForDatePropertyWhenFilterInterestedButNotForPropertyType()
        ////{
        ////    var filter = new StubFilter();
        ////    filter.PropertyInterestGet = () => PropertyInterests.DateTime;
        ////    filter.EvaluateDateProperty = v => ObservationStates.Interested;

        ////    var subject = new StubProjection(filter);
        ////    subject.CallBase = true;

        ////    var count = 0;
        ////    subject.ProjectDateProperty = v => count++;
        ////    subject.Evaluate(new DateProperty());
        ////    count.Should().Be(0);
        ////}
    }
}
